using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;

namespace ReactiveLang
{
    using Util;

    class StatementCollection<T> : Collection<IStatement<T>>, IDisposable
    {
        const int NoDisabling = Int16.MaxValue;
        
        IStatementParent<T> _parent;
        public StatementCollection(IStatementParent<T> grandParent)
        {
            Contract.Requires(grandParent != null);
            _parent = grandParent;
            _disablers = new ObservableCollection<IStatement<T>>();
            _disablers.CollectionChanged += (s, e) => UpdateDisableAfter();
        }
        public bool IsDisposed
        {
            get { return _parent == null; }
        }
        public void Dispose()
        {
            if (IsDisposed)
                return;
            foreach (var handle in _handles)
            {
                if (handle != null)
                    handle.Dispose();
            }
            Items.Clear();
            _handles = null;
            _disableAfter = -1;
            _parent = null;
        }
        void CheckDisposed()
        {
            if (IsDisposed)
                throw new ObjectDisposedException(GetType().Name);
        }

        List<IDisposable> _handles = new List<IDisposable>();
        IDisposable Activate(IStatement<T> stmt)
        {
            if (stmt == null)
                return Disposable.Empty;
            var handle = stmt.ActivateDerived(_parent,
                onReturn : x =>
                {
                    _disablers.Add(stmt);
                    var baseOnReturn = _parent.OnReturn(x);
                    return Disposable.Create(delegate
                    {
                        _disablers.Remove(stmt);
                        baseOnReturn.Dispose();
                    });
                });
            return Disposable.Create(delegate
            {
                handle.Dispose();
                _disablers.Remove(stmt);
            });
        }

        protected override void InsertItem(int index, IStatement<T> stmt)
        {
            CheckDisposed();
            base.InsertItem(index, stmt);

            IDisposable handle = null;
            if (index <= _disableAfter)
            {
                _disableAfter++;
                handle = Activate(stmt);
            }
            _handles.Insert(index, handle);
        }
        protected override void SetItem(int index, IStatement<T> stmt)
        {
            CheckDisposed();
            base.SetItem(index, stmt);

            if (_handles[index] != null)
            {
                _handles[index].Dispose();
                _handles[index] = Activate(stmt);
            }
        }
        protected override void RemoveItem(int index)
        {
            var stmt = this[index];
            CheckDisposed();
            base.RemoveItem(index);

            if (_handles[index] != null)
                _handles[index].Dispose();
            _handles.RemoveAt(index);

            if (index < _disableAfter)
            {
                _disableAfter--;
            }
            else if (index == _disableAfter)
            {
                UpdateDisableAfter();
            }
        }
        protected override void ClearItems()
        {
            CheckDisposed();
            base.ClearItems();
            foreach(var handle in _handles)
                handle.Dispose();
            _handles.Clear();
            UpdateDisableAfter();
        }

        ObservableCollection<IStatement<T>> _disablers;
        public IDisposable MarkAsDisabler(IStatement<T> stmt)
        {
            Contract.Requires(!IsDisposed);
            Contract.Requires(stmt != null);
            Contract.Requires(this.Contains(stmt));
            CheckDisposed();
            _disablers.Add(stmt);
            return Disposable.Create(() => _disablers.Remove(stmt));
        }

        int _disableAfter = NoDisabling;
        void UpdateDisableAfter()
        {
            Contract.Assume(!IsDisposed);

            int newValue = _disablers.Select(IndexOf).Where(i => i >= 0)
                          .Min(i => (int?)i) ?? NoDisabling;
            if (_disableAfter == newValue)
                return;

            int oldValue = _disableAfter;
            _disableAfter = newValue;

            int left = Math.Max(Math.Min(oldValue, newValue) + 1, 0);
            int right = Math.Min(Math.Max(oldValue, newValue), _handles.Count - 1);

            if (oldValue < newValue)
            {
                // go forward, activate statements
                for(int i = left; i <= right ; i++)
                {
                    Contract.Assume(_handles[i] == null);
                    _handles[i] = Activate(this[i]);
                }
            }
            else
            {
                // go backward, deactivate statements
                for (int i = right; i >= left; i--)
                {
                    Contract.Assume(_handles[i] != null);
                    _handles[i].Dispose();
                    _handles[i] = null;
                }
            }
        }
    }
}